<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>未来之窗 - 向量余弦比对工具</title>
    <style>
        :root {
            --primary: #165DFF;
            --secondary: #36CFC9;
            --dark: #1D2129;
            --light: #F2F3F5;
            --gray: #86909C;
            --success: #00B42A;
            --warning: #FF7D00;
            --danger: #F53F3F;
        }
        
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
        
        body {
            background-color: #f7f8fa;
            color: var(--dark);
            line-height: 1.6;
        }
        
        header {
            background: linear-gradient(135deg, var(--primary), var(--secondary));
            color: white;
            padding: 2rem 1rem;
            text-align: center;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
        }
        
        h1 {
            font-size: 2.2rem;
            margin-bottom: 0.5rem;
        }
        
        .subtitle {
            font-size: 1.1rem;
            opacity: 0.9;
            max-width: 800px;
            margin: 0 auto;
        }
        
        .container {
            max-width: 1200px;
            margin: 2rem auto;
            padding: 0 1rem;
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 2rem;
        }
        
        @media (max-width: 768px) {
            .container {
                grid-template-columns: 1fr;
            }
        }
        
        .card {
            background: white;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
            padding: 1.5rem;
            transition: transform 0.2s, box-shadow 0.2s;
        }
        
        .card:hover {
            transform: translateY(-3px);
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
        }
        
        .card h2 {
            color: var(--primary);
            margin-bottom: 1rem;
            padding-bottom: 0.5rem;
            border-bottom: 2px solid var(--light);
        }
        
        .control-group {
            margin-bottom: 1.5rem;
        }
        
        label {
            display: block;
            margin-bottom: 0.5rem;
            font-weight: 500;
            color: var(--dark);
        }
        
        select, input {
            width: 100%;
            padding: 0.75rem;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 1rem;
            margin-bottom: 1rem;
        }
        
        button {
            background-color: var(--primary);
            color: white;
            border: none;
            padding: 0.75rem 1.5rem;
            border-radius: 4px;
            cursor: pointer;
            font-size: 1rem;
            transition: background-color 0.2s;
        }
        
        button:hover {
            background-color: #0E42D2;
        }
        
        button.secondary {
            background-color: var(--gray);
        }
        
        button.secondary:hover {
            background-color: #6B7785;
        }
        
        button.success {
            background-color: var(--success);
        }
        
        button.success:hover {
            background-color: #008722;
        }
        
        .button-group {
            display: flex;
            gap: 0.75rem;
            margin-bottom: 1.5rem;
            flex-wrap: wrap;
        }
        
        .result-section {
            margin-top: 1.5rem;
        }
        
        .result-section h3 {
            margin-bottom: 0.75rem;
            color: var(--dark);
        }
        
        .result-card {
            background-color: var(--light);
            border-radius: 6px;
            padding: 1rem;
            margin-bottom: 1rem;
        }
        
        .similarity-score {
            font-size: 2rem;
            font-weight: bold;
            color: var(--primary);
            text-align: center;
            margin: 1rem 0;
        }
        
        .similarity-label {
            text-align: center;
            padding: 0.5rem;
            border-radius: 4px;
            font-weight: 500;
            margin-bottom: 1rem;
        }
        
        .high {
            background-color: rgba(0, 180, 42, 0.1);
            color: var(--success);
        }
        
        .medium {
            background-color: rgba(255, 125, 0, 0.1);
            color: var(--warning);
        }
        
        .low {
            background-color: rgba(245, 63, 63, 0.1);
            color: var(--danger);
        }
        
        .vector-display {
            max-height: 200px;
            overflow-y: auto;
            font-family: monospace;
            background-color: #f8f9fa;
            border-radius: 4px;
            padding: 0.75rem;
            margin-bottom: 1rem;
            font-size: 0.9rem;
            white-space: pre-wrap;
        }
        
        .vector-list {
            max-height: 300px;
            overflow-y: auto;
            border: 1px solid #eee;
            border-radius: 4px;
            margin-bottom: 1rem;
        }
        
        .vector-item {
            padding: 0.75rem;
            border-bottom: 1px solid #eee;
            cursor: pointer;
            transition: background-color 0.2s;
        }
        
        .vector-item:hover {
            background-color: var(--light);
        }
        
        .vector-item:last-child {
            border-bottom: none;
        }
        
        .vector-item.selected {
            background-color: rgba(22, 93, 255, 0.1);
            border-left: 3px solid var(--primary);
        }
        
        .add-vector-form {
            background-color: var(--light);
            padding: 1rem;
            border-radius: 6px;
        }
        
        .form-group {
            margin-bottom: 1rem;
        }
        
        .instructions {
            background-color: rgba(22, 93, 255, 0.05);
            border-left: 3px solid var(--primary);
            padding: 1rem;
            margin-bottom: 1.5rem;
            border-radius: 0 4px 4px 0;
        }
        
        .instructions h4 {
            margin-bottom: 0.5rem;
            color: var(--primary);
        }
        
        footer {
            text-align: center;
            padding: 2rem 1rem;
            color: var(--gray);
            border-top: 1px solid var(--light);
            margin-top: 2rem;
        }
    </style>
</head>
<body>
    <header>
        <h1>未来之窗 - 向量余弦比对工具</h1>
        <p class="subtitle">通过余弦相似度算法比对向量数据，探索数据间的潜在关联</p>
    </header>
    
    <div class="container">
        <div class="card">
            <h2>向量比对</h2>
            
            <div class="instructions">
                <h4>使用说明</h4>
                <p>选择两个向量，点击"计算余弦相似度"按钮，查看两个向量的相似程度。值越接近1表示越相似，越接近0表示差异越大。</p>
            </div>
            
            <div class="control-group">
                <label for="vector1">选择第一个向量</label>
                <select id="vector1"></select>
            </div>
            
            <div class="control-group">
                <label for="vector2">选择第二个向量</label>
                <select id="vector2"></select>
            </div>
            
            <div class="button-group">
                <button id="compareBtn">计算余弦相似度</button>
                <button id="randomCompareBtn" class="secondary">随机选择比对</button>
            </div>
            
            <div class="result-section">
                <h3>比对结果</h3>
                <div class="result-card">
                    <div class="similarity-score" id="similarityScore">-</div>
                    <div class="similarity-label" id="similarityLabel">请选择向量进行比对</div>
                    
                    <div>
                        <h4>向量1详情:</h4>
                        <div class="vector-display" id="vector1Display"></div>
                    </div>
                    
                    <div>
                        <h4>向量2详情:</h4>
                        <div class="vector-display" id="vector2Display"></div>
                    </div>
                </div>
            </div>
        </div>
        
        <div class="card">
            <h2>向量管理</h2>
            
            <div class="add-vector-form">
                <h3>添加新向量</h3>
                <div class="form-group">
                    <label for="vectorName">向量名称</label>
                    <input type="text" id="vectorName" placeholder="例如：文档向量1">
                </div>
                <div class="form-group">
                    <label for="vectorData">向量数据 (JSON格式数组)</label>
                    <input type="text" id="vectorData" placeholder="例如：[0.1, 0.5, 0.3, 0.8, 0.2]">
                </div>
                <button id="addVectorBtn" class="success">添加向量</button>
            </div>
            
            <div class="control-group">
                <label>向量列表 (<span id="vectorCount">0</span>)</label>
                <div class="vector-list" id="vectorList"></div>
            </div>
            
            <div class="button-group">
                <button id="exportBtn">导出所有向量 (JSON)</button>
                <button id="clearBtn" class="secondary">清空向量列表</button>
            </div>
        </div>
    </div>
    
    <footer>
        未来之窗向量比对工具 &copy; 2023 - 基于余弦相似度算法
    </footer>

    <script>
        // 向量比对模块
        const VectorComparator = (() => {
            class VectorComparator {
                /**
                 * 计算两个向量的点积
                 * @param {number[]} vec1 - 第一个向量
                 * @param {number[]} vec2 - 第二个向量
                 * @returns {number} 点积结果
                 */
                dotProduct(vec1, vec2) {
                    if (vec1.length !== vec2.length) {
                        throw new Error('向量长度必须相同才能计算点积');
                    }
                    
                    let product = 0;
                    for (let i = 0; i < vec1.length; i++) {
                        product += vec1[i] * vec2[i];
                    }
                    return product;
                }
                
                /**
                 * 计算向量的模长
                 * @param {number[]} vec - 向量
                 * @returns {number} 模长
                 */
                magnitude(vec) {
                    let sum = 0;
                    for (let i = 0; i < vec.length; i++) {
                        sum += vec[i] * vec[i];
                    }
                    return Math.sqrt(sum);
                }
                
                /**
                 * 计算两个向量的余弦相似度
                 * @param {number[]} vec1 - 第一个向量
                 * @param {number[]} vec2 - 第二个向量
                 * @returns {number} 余弦相似度值 (0-1之间)
                 */
                cosineSimilarity(vec1, vec2) {
                    if (vec1.length !== vec2.length) {
                        throw new Error('向量长度必须相同才能计算余弦相似度');
                    }
                    
                    const dotProd = this.dotProduct(vec1, vec2);
                    const mag1 = this.magnitude(vec1);
                    const mag2 = this.magnitude(vec2);
                    
                    if (mag1 === 0 || mag2 === 0) {
                        return 0; // 避免除以零
                    }
                    
                    return parseFloat((dotProd / (mag1 * mag2)).toFixed(6));
                }
                
                /**
                 * 批量计算一个向量与多个向量的余弦相似度
                 * @param {number[]} targetVec - 目标向量
                 * @param {Array<{id: string, name: string, vector: number[]}>} vectors - 向量数组
                 * @returns {Array<{id: string, name: string, similarity: number}>} 相似度结果数组
                 */
                batchCompare(targetVec, vectors) {
                    return vectors.map(vecData => ({
                        id: vecData.id,
                        name: vecData.name,
                        similarity: this.cosineSimilarity(targetVec, vecData.vector)
                    })).sort((a, b) => b.similarity - a.similarity);
                }
            }
            
            return VectorComparator;
        })();
        
        // 向量管理模块
        const VectorManager = (() => {
            class VectorManager {
                constructor() {
                    this.vectors = []; // 存储向量数据 {id, name, vector}
                    this.nextId = 1;   // 用于生成唯一ID
                }
                
                /**
                 * 添加向量
                 * @param {string} name - 向量名称
                 * @param {number[]} vector - 向量数据
                 * @returns {string} 新添加向量的ID
                 */
                addVector(name, vector) {
                    if (!Array.isArray(vector) || vector.length === 0) {
                        throw new Error('向量必须是非空数组');
                    }
                    
                    // 验证向量是否包含有效数字
                    if (!vector.every(v => typeof v === 'number' && !isNaN(v))) {
                        throw new Error('向量必须只包含数字');
                    }
                    
                    const id = `vec-${this.nextId++}`;
                    this.vectors.push({ id, name, vector });
                    return id;
                }
                
                /**
                 * 获取所有向量
                 * @returns {Array<{id: string, name: string, vector: number[]}>} 向量数组
                 */
                getAllVectors() {
                    return [...this.vectors];
                }
                
                /**
                 * 根据ID获取向量
                 * @param {string} id - 向量ID
                 * @returns {Object|null} 向量数据或null
                 */
                getVectorById(id) {
                    return this.vectors.find(vec => vec.id === id) || null;
                }
                
                /**
                 * 删除向量
                 * @param {string} id - 向量ID
                 * @returns {boolean} 删除成功返回true
                 */
                removeVector(id) {
                    const initialLength = this.vectors.length;
                    this.vectors = this.vectors.filter(vec => vec.id !== id);
                    return this.vectors.length < initialLength;
                }
                
                /**
                 * 清空所有向量
                 */
                clearAllVectors() {
                    this.vectors = [];
                }
                
                /**
                 * 导出所有向量为JSON
                 * @returns {string} JSON字符串
                 */
                exportToJSON() {
                    return JSON.stringify(this.vectors, null, 2);
                }
                
                /**
                 * 从JSON导入向量
                 * @param {string} jsonStr - JSON字符串
                 * @returns {number} 导入的向量数量
                 */
                importFromJSON(jsonStr) {
                    try {
                        const vectors = JSON.parse(jsonStr);
                        if (!Array.isArray(vectors)) {
                            throw new Error('导入的数据必须是数组');
                        }
                        
                        let count = 0;
                        vectors.forEach(vec => {
                            if (vec.name && Array.isArray(vec.vector)) {
                                this.addVector(vec.name, vec.vector);
                                count++;
                            }
                        });
                        
                        return count;
                    } catch (e) {
                        console.error('导入失败:', e);
                        throw new Error('导入失败: ' + e.message);
                    }
                }
                
                /**
                 * 获取向量数量
                 * @returns {number} 向量数量
                 */
                getVectorCount() {
                    return this.vectors.length;
                }
                
                /**
                 * 随机获取一个向量
                 * @returns {Object|null} 随机向量或null
                 */
                getRandomVector() {
                    if (this.vectors.length === 0) return null;
                    const randomIndex = Math.floor(Math.random() * this.vectors.length);
                    return this.vectors[randomIndex];
                }
            }
            
            return VectorManager;
        })();
        
        // 生成100个演示向量数据
        function generateDemoVectors(count = 100) {
            const vectors = [];
            
            // 生成5个基础向量作为"原型"
            const baseVectors = [];
            for (let i = 0; i < 5; i++) {
                const baseVec = [];
                // 生成10维向量
                for (let j = 0; j < 10; j++) {
                    baseVec.push(parseFloat((Math.random() * 0.8 + 0.2).toFixed(4)));
                }
                baseVectors.push(baseVec);
            }
            
            // 基于基础向量生成相似向量
            for (let i = 0; i < count; i++) {
                // 随机选择一个基础向量
                const baseIndex = Math.floor(Math.random() * baseVectors.length);
                const baseVec = baseVectors[baseIndex];
                
                // 在基础向量上添加一些噪声，生成相似向量
                const noisyVec = baseVec.map(value => {
                    // 添加-10%到+10%的噪声
                    const noise = value * (Math.random() * 0.2 - 0.1);
                    return parseFloat((value + noise).toFixed(4));
                });
                
                vectors.push({
                    name: `演示向量 ${i + 1} (类别 ${baseIndex + 1})`,
                    vector: noisyVec
                });
            }
            
            return vectors;
        }
        
        // 页面交互逻辑
        document.addEventListener('DOMContentLoaded', () => {
            // 初始化向量管理器和比对器
            const vectorManager = new VectorManager();
            const comparator = new VectorComparator();
            
            // DOM元素
            const vector1Select = document.getElementById('vector1');
            const vector2Select = document.getElementById('vector2');
            const compareBtn = document.getElementById('compareBtn');
            const randomCompareBtn = document.getElementById('randomCompareBtn');
            const similarityScore = document.getElementById('similarityScore');
            const similarityLabel = document.getElementById('similarityLabel');
            const vector1Display = document.getElementById('vector1Display');
            const vector2Display = document.getElementById('vector2Display');
            const vectorNameInput = document.getElementById('vectorName');
            const vectorDataInput = document.getElementById('vectorData');
            const addVectorBtn = document.getElementById('addVectorBtn');
            const vectorList = document.getElementById('vectorList');
            const vectorCount = document.getElementById('vectorCount');
            const exportBtn = document.getElementById('exportBtn');
            const clearBtn = document.getElementById('clearBtn');
            
            // 更新向量选择下拉框
            function updateVectorSelects() {
                const vectors = vectorManager.getAllVectors();
                
                // 保存当前选中值
                const current1 = vector1Select.value;
                const current2 = vector2Select.value;
                
                // 清空下拉框
                vector1Select.innerHTML = '';
                vector2Select.innerHTML = '';
                
                // 添加选项
                vectors.forEach(vec => {
                    const option1 = document.createElement('option');
                    option1.value = vec.id;
                    option1.textContent = vec.name;
                    vector1Select.appendChild(option1);
                    
                    const option2 = document.createElement('option');
                    option2.value = vec.id;
                    option2.textContent = vec.name;
                    vector2Select.appendChild(option2);
                });
                
                // 恢复选中值（如果存在）
                if (current1 && vectors.some(v => v.id === current1)) {
                    vector1Select.value = current1;
                }
                if (current2 && vectors.some(v => v.id === current2)) {
                    vector2Select.value = current2;
                }
            }
            
            // 更新向量列表显示
            function updateVectorList() {
                const vectors = vectorManager.getAllVectors();
                vectorList.innerHTML = '';
                
                vectors.forEach(vec => {
                    const item = document.createElement('div');
                    item.className = 'vector-item';
                    item.dataset.id = vec.id;
                    item.innerHTML = `
                        <strong>${vec.name}</strong>
                        <div style="font-size: 0.8rem; color: var(--gray);">
                            维度: ${vec.vector.length}
                        </div>
                    `;
                    
                    item.addEventListener('click', () => {
                        // 切换选中状态
                        document.querySelectorAll('.vector-item').forEach(i => {
                            i.classList.remove('selected');
                        });
                        item.classList.add('selected');
                    });
                    
                    vectorList.appendChild(item);
                });
                
                vectorCount.textContent = vectors.length;
            }
            
            // 显示向量详情
            function displayVectorDetails(vecId, element) {
                const vec = vectorManager.getVectorById(vecId);
                if (vec) {
                    element.textContent = `名称: ${vec.name}\n向量: [${vec.vector.join(', ')}]`;
                } else {
                    element.textContent = '未选择向量';
                }
            }
            
            // 更新相似度标签
            function updateSimilarityLabel(score) {
                if (score >= 0.8) {
                    similarityLabel.textContent = '极高相似度';
                    similarityLabel.className = 'similarity-label high';
                } else if (score >= 0.5) {
                    similarityLabel.textContent = '中等相似度';
                    similarityLabel.className = 'similarity-label medium';
                } else {
                    similarityLabel.textContent = '低相似度';
                    similarityLabel.className = 'similarity-label low';
                }
            }
            
            // 比对向量
            function compareVectors() {
                const vec1Id = vector1Select.value;
                const vec2Id = vector2Select.value;
                
                if (!vec1Id || !vec2Id) {
                    alert('请选择两个向量进行比对');
                    return;
                }
                
                if (vec1Id === vec2Id) {
                    alert('请选择不同的向量进行比对');
                    return;
                }
                
                const vec1 = vectorManager.getVectorById(vec1Id);
                const vec2 = vectorManager.getVectorById(vec2Id);
                
                try {
                    const score = comparator.cosineSimilarity(vec1.vector, vec2.vector);
                    similarityScore.textContent = score;
                    updateSimilarityLabel(score);
                    
                    // 显示向量详情
                    displayVectorDetails(vec1Id, vector1Display);
                    displayVectorDetails(vec2Id, vector2Display);
                } catch (e) {
                    alert('比对失败: ' + e.message);
                }
            }
            
            // 随机选择向量进行比对
            function randomCompare() {
                const vectors = vectorManager.getAllVectors();
                if (vectors.length < 2) {
                    alert('至少需要两个向量才能进行比对');
                    return;
                }
                
                // 随机选择两个不同的向量
                let index1 = Math.floor(Math.random() * vectors.length);
                let index2 = Math.floor(Math.random() * vectors.length);
                
                while (index1 === index2) {
                    index2 = Math.floor(Math.random() * vectors.length);
                }
                
                vector1Select.value = vectors[index1].id;
                vector2Select.value = vectors[index2].id;
                
                compareVectors();
            }
            
            // 添加新向量
            function addVector() {
                const name = vectorNameInput.value.trim();
                const vectorStr = vectorDataInput.value.trim();
                
                if (!name) {
                    alert('请输入向量名称');
                    return;
                }
                
                if (!vectorStr) {
                    alert('请输入向量数据');
                    return;
                }
                
                try {
                    const vector = JSON.parse(vectorStr);
                    if (!Array.isArray(vector) || vector.length === 0) {
                        throw new Error('向量必须是非空数组');
                    }
                    
                    // 验证向量元素是否为数字
                    vector.forEach((val, index) => {
                        if (typeof val !== 'number' || isNaN(val)) {
                            throw new Error(`向量第${index + 1}个元素不是有效数字`);
                        }
                    });
                    
                    // 添加向量
                    vectorManager.addVector(name, vector);
                    
                    // 重置输入
                    vectorNameInput.value = '';
                    vectorDataInput.value = '';
                    
                    // 更新UI
                    updateVectorSelects();
                    updateVectorList();
                    
                    alert('向量添加成功');
                } catch (e) {
                    alert('添加失败: ' + e.message);
                }
            }
            
            // 导出向量
            function exportVectors() {
                if (vectorManager.getVectorCount() === 0) {
                    alert('没有向量可导出');
                    return;
                }
                
                const jsonStr = vectorManager.exportToJSON();
                const blob = new Blob([jsonStr], { type: 'application/json' });
                const url = URL.createObjectURL(blob);
                
                const a = document.createElement('a');
                a.href = url;
                a.download = 'vectors_' + new Date().getTime() + '.json';
                document.body.appendChild(a);
                a.click();
                
                // 清理
                setTimeout(() => {
                    document.body.removeChild(a);
                    URL.revokeObjectURL(url);
                }, 0);
            }
            
            // 清空向量
            function clearVectors() {
                if (vectorManager.getVectorCount() === 0) {
                    alert('向量列表已经为空');
                    return;
                }
                
                if (confirm('确定要清空所有向量吗？此操作不可恢复。')) {
                    vectorManager.clearAllVectors();
                    updateVectorSelects();
                    updateVectorList();
                    similarityScore.textContent = '-';
                    similarityLabel.textContent = '请选择向量进行比对';
                    vector1Display.textContent = '';
                    vector2Display.textContent = '';
                }
            }
            
            // 事件监听
            compareBtn.addEventListener('click', compareVectors);
            randomCompareBtn.addEventListener('click', randomCompare);
            addVectorBtn.addEventListener('click', addVector);
            exportBtn.addEventListener('click', exportVectors);
            clearBtn.addEventListener('click', clearVectors);
            
            // 初始化演示数据
            const demoVectors = generateDemoVectors(100);
            demoVectors.forEach(vec => {
                vectorManager.addVector(vec.name, vec.vector);
            });
            
            // 初始化UI
            updateVectorSelects();
            updateVectorList();
        });
    </script>
</body>
</html>
    